#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <errno.h>
#include <sstream>
#include "game_log.h"

using namespace std;

string get_hour()
{
	static char buf[20];
	stringstream sstr;
	time_t t;
	struct tm gt;
	struct tm* p;
	int len;
	time(&t);
	p = localtime_r(&t, &gt);
	if (!p)
	p = &gt;
	len = sprintf(buf, "%.4d%.2d%.2d%.2d",
				p->tm_year + 1900,
				p->tm_mon + 1,
				p->tm_mday,
				p->tm_hour);
	return string(buf, len);
}

enum LOG_TYPE {
    PANIC_TYPE = 0,
    FATAL_TYPE,
    ERROR_TYPE,
    WARN_TYPE,
    INFO_TYPE,
    DEBUG_TYPE,
};

const char *log_type_str[] = {"panic", "fatal", "error", "warn ", "info ", "debug"};

#define COMMON_LOG(log_type) \
	static char buf[1024000]; \
	time_t t;\
    struct tm gt;\
    struct tm* p;\
    int len;\
    int cnt;\
	time(&t);\
    p = localtime_r(&t, &gt);\
    if(!p)\
        p = &gt;\
	len = sprintf(buf, "[%s][%.4d-%.2d-%.2d %.2d:%.2d:%.2d] ",\
                log_type_str[log_type],\
                p->tm_year + 1900,\
                p->tm_mon + 1,\
                p->tm_mday,\
                p->tm_hour,\
                p->tm_min,\
                p->tm_sec);\
	va_list argptr;\
	va_start(argptr, fmt);\
    cnt = vsprintf(buf + len, fmt, argptr);\
	va_end(argptr);\
    cnt += len;\


GameLog::GameLog()
{
	_level = LEVEL_DEBUG;
	_fd = -1;
	_write_db = WRITE_DB_ON;
	_rotate = ROTATE_OFF;
	_max_size = 0;
	_current_size = 0;
	_max_file = 0;
	_server_id = 0;
	_hour_str = get_hour();
#ifdef POSIX_PTHREAD
	pthread_mutex_init(&_mutex, NULL);
#endif
}

GameLog::~GameLog()
{
	stop();
#ifdef POSIX_PTHREAD
	pthread_mutex_destroy(&_mutex);
#endif
}

void GameLog::start(int server_id, int level, int write_db, int rotate, int64_t max_size = 1073741824, int max_file = 50)
{
	struct stat st;
	_server_id = server_id;
	_log_file = file_name();
	_fd = open(_log_file.c_str(), O_RDWR | O_APPEND | O_CREAT, 0644);
	if (_fd < 0) {
		perror("LOG~open");
		exit(1);
	}

	if (fstat(_fd, &st) < 0) {
		perror("LOG~stat64");
		exit(1);
	}

	_level = level;
	_write_db = write_db;
	_rotate = rotate;
	if (max_size > 0)
		_max_size = max_size;
	_current_size = st.st_size;
	_max_file = max_file;
}

void GameLog::stop()
{
	if (_fd != -1) {
		close(_fd);
		_fd = -1;
	}
}

string GameLog::file_name()
{
	return "log/" + to_string(_server_id) + "/" + _hour_str + ".log";
}

void GameLog::output(char *buf, int cnt)
{
	printf("%s", buf);
	if (_write_db == WRITE_DB_OFF) {
		return;
	}

#ifdef POSIX_PTHREAD	
	pthread_mutex_lock(&_mutex);
#endif
	if (_fd > 0) {
		if (get_hour() != _hour_str)
		{
			rotate_hour();
		}
		if (_rotate == ROTATE_ON) {
			if (_current_size > _max_size) {
				rotate();
			}
		}
		int ret = write(_fd, buf, cnt);
		if (ret < 0) {
			// todo
			perror("Log~write");
		}
		_current_size += cnt;
	}
	else {
		printf("%s", buf);
	}
#ifdef POSIX_PTHREAD
	pthread_mutex_unlock(&_mutex);
#endif
}

void GameLog::rotate()
{
	char src[1024];
	char dst[1024];

	close(_fd);
	_fd = -1;
	_current_size = 0;

	for (int i = _max_file; i > 0; i--) {
		snprintf(dst, sizeof(dst), "%s.%d", _log_file.c_str(), i);
		snprintf(src, sizeof(src), "%s.%d", _log_file.c_str(), i - 1);
		rename(src, dst);
		// printf("rename %s %s\n",src ,dst);
	}

	snprintf(dst, sizeof(dst), "%s.%d", _log_file.c_str(), 0);
	snprintf(src, sizeof(src), "%s", _log_file.c_str());
	rename(src, dst);
	// printf("rename %s %s\n",src ,dst);

	_fd = open(_log_file.c_str(), O_RDWR | O_APPEND | O_CREAT, 0644);
	if (_fd < 0) {
		perror("LOG~open");
		return;
	}
}

void GameLog::rotate_hour()
{
	close(_fd);
	_fd = -1;
	_current_size = 0;
	_hour_str = get_hour();
	_log_file = file_name();
	_fd = open(_log_file.c_str(), O_RDWR | O_APPEND | O_CREAT, 0644);
	if (_fd < 0) {
		perror("LOG~open");
		return;
	}
}

void GameLog::panic(const char *fmt, ...)
{
	if (_level < LEVEL_PANIC)
		return;
	int log_type = PANIC_TYPE;
	COMMON_LOG(log_type)

		output(buf, cnt);

	exit(1);
}

void GameLog::fatal(const char *fmt, ...)
{
	if (_level < LEVEL_FATAL)
		return;
	int log_type = FATAL_TYPE;
	COMMON_LOG(log_type)

		output(buf, cnt);
}

void GameLog::error(const char *fmt, ...)
{
	if (_level < LEVEL_ERROR)
		return;
	int log_type = ERROR_TYPE;
	COMMON_LOG(log_type)

		output(buf, cnt);
}

void GameLog::warn(const char *fmt, ...)
{
	if (_level < LEVEL_WARN)
		return;
	int log_type = WARN_TYPE;
	COMMON_LOG(log_type)

		output(buf, cnt);
}

void GameLog::info(const char *fmt, ...)
{
	if (_level < LEVEL_INFO)
		return;
	int log_type = INFO_TYPE;
	COMMON_LOG(log_type)

		output(buf, cnt);
}

void GameLog::debug(const char *fmt, ...)
{
	if (_level < LEVEL_DEBUG)
		return;
	int log_type = DEBUG_TYPE;
	COMMON_LOG(log_type)

		output(buf, cnt);
}

#if 0
int main()
{
	Log log;
	GAME_LOG.warn("%s\n", "if is good.");
	GAME_LOG.start("a.log", LEVEL_DEBUG, CONSOLE_OFF, ROTATE_ON, 100, 30);
	GAME_LOG.fatal("%s\n", "if is good.");
	GAME_LOG.error("%s\n", "if is good.");
	GAME_LOG.warn("%s\n", "if is good.");
	GAME_LOG.info("%s\n", "if is good.");
	GAME_LOG.debug("%s\n", "if is good.");
	GAME_LOG.panic("%s\n", "if is good.");
	GAME_LOG.stop();
	
	return 0;
}
#endif
